package com.hndfsj.minio.service;

import com.hndfsj.minio.config.MinioProperties;
import io.minio.*;
import io.minio.errors.*;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.util.*;

public class MinioFileService {
    private static final Logger LOGGER = LoggerFactory.getLogger(MinioFileService.class);
    @Autowired
    private MinioProperties properties;
    @Autowired
    private MinioTempCleanService minioTempCleanService;
    @Autowired
    private MinioClientService minioClientService;
    private String endpoint;
    private String bucket;
    private String accessKey;
    private String secretKey;
    private String tmpDir;
    /**
     * url分隔符
     */
    public static final String URI_DELIMITER = "/";

    public MinioFileService() {
    }

    @PostConstruct
    private void init() {
        this.endpoint = this.properties.getEndpoint();
        this.bucket = this.properties.getBucket();
        this.accessKey = this.properties.getAccessKey();
        this.secretKey = this.properties.getSecretKey();
        this.tmpDir = this.properties.getTmpDir();
        if (this.properties.getTmpClean()) {
            Date firstTmpCleanTime = this.properties.getTmpFirstCleanTime();
            if (firstTmpCleanTime == null) {
                firstTmpCleanTime = new Date();
            }

            Long tmpCleanPeriod = this.properties.getTmpCleanPeriod();
            Long tmpAliveDuration = this.properties.getTmpAliveDuration();
            this.initTmpCleaner(firstTmpCleanTime, tmpCleanPeriod, tmpAliveDuration);
        }

    }

    /**
     * 清除缓存区
     * @param time
     * @param period
     * @param duration
     */
    private void initTmpCleaner(Date time, Long period, Long duration) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            public void run() {
                MinioFileService.this.minioTempCleanService.cleanTemp(MinioFileService.this.tmpDir, duration);
            }
        }, time, period);
    }

    /**
     * 获取Minio连接
     * @return
     */
    private MinioClient connect() {
        MinioClient minioClient = this.minioClientService.getClient(this.endpoint, this.accessKey, this.secretKey);
        LOGGER.debug("Got the client to minIO server {}.", this.endpoint);
        return minioClient;
    }

    /**
     * 创建bucket
     * @param bucketName
     * @return
     */
    public boolean createBucket(String bucketName) {

        try {
            MinioClient connect = this.connect();
            if (connect.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
                return true;
            } else {
                connect.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                return true;
            }
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
            LOGGER.error("error: {}", e.getMessage());
            return false;
        }

    }

    /**
     * 删除bucket
     * @param bucketName
     * @return
     */
    public boolean deleteBucket(String bucketName) {
        try {
            MinioClient connect = this.connect();
            if (connect.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
                connect.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
                return true;
            } else {
                return true;
            }
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
            LOGGER.error("error: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 上传文件
     * @param file
     * @param fileName
     * @return
     */
    public String putObject(File file, String fileName) {
        return this.putObject(this.bucket, file, fileName);
    }

    /**
     * 删除文件
     * @param fileName
     * @return
     */
    public boolean delete(String fileName) {
        return this.delete(this.bucket, fileName);
    }

    /**
     * 获取文件输入流
     * @param fileName
     * @return
     */
    public InputStream getStream(String fileName) {
        return this.getStream(this.bucket, fileName);
    }

    /**
     * 获取文件
     * @param fileName
     * @return
     */
    public File getFile(String fileName) {
        return this.getFile(this.bucket, fileName);
    }

    /**
     * 将文件写入输出流
     * @param fileName
     * @param os
     * @throws IOException
     */
    public void writeTo(String fileName, OutputStream os) throws IOException {
        this.writeTo(this.bucket, fileName, os);
    }

    /**
     * 上传文件
     *
     * @param multipartFile
     * @return
     */
    public String putObject(MultipartFile multipartFile) {
        return putObject(new MultipartFile[]{multipartFile}).get(0);
    }

    /**
     * 上传文件
     * @param bucket
     * @param file
     * @return
     */
    public String putObject(String bucket, File file) {
        return this.putObject(bucket, (File) file, (String) null);
    }

    /**
     * 上传文件
     * @param bucket
     * @param file
     * @param destFileName
     * @return
     */
    public String putObject(String bucket, File file, String destFileName) {
        if (bucket != null && bucket.length() > 0) {
            if (destFileName == null || destFileName.length() <= 0) {
                destFileName = file.getName();
            }

            try {
                MinioClient minioClient = this.connect();
                String[] folders = getDateFolder();
                // 年/月/日/file
                String finalPath = new StringBuilder(String.join(URI_DELIMITER, folders))
                        .append(URI_DELIMITER)
                        .append(destFileName).toString();
                this.checkBucket(minioClient, bucket);
                minioClient.putObject(PutObjectArgs.builder()
                        .stream(new FileInputStream(file), file.length(), PutObjectArgs.MIN_MULTIPART_SIZE)
                        .object(finalPath)
                        .bucket(this.bucket)
                        .build());
                return finalPath;
            } catch (NoSuchAlgorithmException | InsufficientDataException | IOException | InvalidKeyException | ErrorResponseException | InternalException | InvalidResponseException | ServerException | XmlParserException e) {
                LOGGER.error("error: {}", e.getMessage());
                return null;
            }

        } else {
            LOGGER.error("bucket不能为空.");
            return null;
        }
    }

    /**
     * 判断 bucket 是否存在，不存在则创建
     *
     * @param minioClient
     * @param bucket
     * @throws ServerException
     * @throws InsufficientDataException
     * @throws ErrorResponseException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws InvalidResponseException
     * @throws XmlParserException
     * @throws InternalException
     */
    private void checkBucket(MinioClient minioClient, String bucket) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
        if (isExist) {
            LOGGER.info("bucket {} 已经存在.", bucket);
        } else {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
        }

    }

    /**
     * 保存文件
     *
     * @param bucket
     * @param is
     * @param destFileName
     * @return
     */
    public String putObject(String bucket, InputStream is, String destFileName) {
        if (bucket != null && bucket.length() > 0) {
            try {
                MinioClient minioClient = this.connect();
                String[] folders = getDateFolder();
                // 年/月/日/file
                String finalPath = new StringBuilder(String.join(URI_DELIMITER, folders))
                        .append(URI_DELIMITER)
                        .append(destFileName).toString();
                this.checkBucket(minioClient, bucket);
                minioClient.putObject(PutObjectArgs.builder()
                        .stream(is, is.available(), PutObjectArgs.MIN_MULTIPART_SIZE)
                        .object(finalPath)
                        .bucket(this.bucket)
                        .build());
                return destFileName;
            } catch (ServerException | InsufficientDataException | ErrorResponseException | IOException | NoSuchAlgorithmException | InvalidKeyException | InvalidResponseException | XmlParserException | InternalException e) {
                LOGGER.error("error: {}", e.getMessage());
                return null;
            }

        } else {
            LOGGER.error("bucket不能为空.");
            return null;
        }
    }

    /**
     * 删除文件
     *
     * @param bucket
     * @param fileName
     * @return
     */
    public boolean delete(String bucket, String fileName) {
        try {
            MinioClient connect = this.connect();
            connect.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(fileName).build());
            return true;
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
            LOGGER.error("error: {}", e.getMessage());
            return false;
        }

    }

    /**
     * 获取输入流InputStream
     *
     * @param bucket
     * @param fileName
     * @return
     */
    public InputStream getStream(String bucket, String fileName) {
        InputStream is = null;

        try {
            MinioClient minioClient = this.connect();
            is = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(fileName).build());
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
            LOGGER.error("error: {}", e.getMessage());
        }

        return is;
    }

    /**
     * 获取文件
     *
     * @param bucket
     * @param fileName
     * @return
     */
    public File getFile(String bucket, String fileName) {
        InputStream is = this.getStream(bucket, fileName);
        File dir = new File(this.tmpDir);
        if (!dir.exists() || dir.isFile()) {
            dir.mkdirs();
        }

        File file = new File(this.tmpDir + fileName);

        try {
            FileUtils.copyToFile(is, file);
        } catch (IOException var7) {
            LOGGER.error("error: {}", var7.getMessage());
        }

        return file;
    }

    /**
     * 文件放入OutputStream流
     *
     * @param bucket
     * @param fileName
     * @param os
     * @throws IOException
     */
    public void writeTo(String bucket, String fileName, OutputStream os) throws IOException {
        InputStream is = this.getStream(bucket, fileName);
        byte[] bytes = new byte[1024];

        int len;
        while ((len = is.read(bytes)) != -1) {
            os.write(bytes, 0, len);
        }

        os.flush();
    }










    /**
     * 上传文件
     *
     * @param multipartFiles
     * @return
     */
    public List<String> putObject(MultipartFile... multipartFiles) {
        try {
            MinioClient minioClient = this.connect();
            List<String> retVal = new LinkedList<>();
            String[] folders = getDateFolder();
            for (MultipartFile multipartFile : multipartFiles) {
                // UUID重命名
                String fileName = UUID.randomUUID().toString().replace("-", "") + "." + getSuffix(multipartFile.getOriginalFilename());
                // 年/月/日/file
                String finalPath = new StringBuilder(String.join(URI_DELIMITER, folders))
                        .append(URI_DELIMITER)
                        .append(fileName).toString();
                this.checkBucket(minioClient, bucket);
                minioClient.putObject(PutObjectArgs.builder()
                        .stream(multipartFile.getInputStream(), multipartFile.getSize(), PutObjectArgs.MIN_MULTIPART_SIZE)
                        .object(finalPath)
                        .contentType(multipartFile.getContentType())
                        .bucket(this.bucket)
                        .build());

                retVal.add(finalPath);
            }
            return retVal;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 复制文件
     *
     * @param source
     * @param target
     */
    public void copy(String source, String target) {


        MinioClient minioClient = this.connect();
        try {
            minioClient.copyObject(CopyObjectArgs.builder()
                    .bucket(this.bucket)
                    .object(target)
                    .source(CopySource.builder()
                            .bucket(this.bucket)
                            .object(source)
                            .build())
                    .build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 获取文件后缀
     *
     * @param fileName
     * @return
     */
    protected static String getSuffix(String fileName) {
        int index = fileName.lastIndexOf(".");
        if (index != -1) {
            String suffix = fileName.substring(index + 1);
            if (!suffix.isEmpty()) {
                return suffix;
            }
        }
        throw new IllegalArgumentException("非法文件名称：" + fileName);
    }

    /**
     * 获取年月日[2020, 09, 01]
     *
     * @return
     */
    protected static String[] getDateFolder() {
        String[] retVal = new String[3];

        LocalDate localDate = LocalDate.now();
        retVal[0] = localDate.getYear() + "";

        int month = localDate.getMonthValue();
        retVal[1] = month < 10 ? "0" + month : month + "";

        int day = localDate.getDayOfMonth();
        retVal[2] = day < 10 ? "0" + day : day + "";

        return retVal;
    }
}

